<script setup>
import { ref, watch } from 'vue';
import clipboard3 from 'vue-clipboard3'
import { marked } from 'marked';
import Docxtemplater from 'docxtemplater'
import { saveAs } from 'file-saver';
import PizZip from 'pizzip'
import PizZipUtils from 'pizzip/utils/index'
import { io } from 'socket.io-client'

// 接口
const socket = ref(null)
// 接收后端数据
const data = ref(null)
// 修复建议窗口
const centerDialogVisible = ref(false)
// 扫描报告窗口
const scanPaperVisible = ref(false)
// 漏洞列表
const loopList = ref([]);
// 漏洞列表反转（新的在上面）
const loopListRe = ref([...loopList.value].reverse())
// 是否扫描 false为未在扫描，true为正在扫描
const loading = ref(false)
// 是否结束扫描 主要控制扫描样式 0：扫描前 1：扫描中 2：扫描结束
const isEnd = ref(0)
// 是否扫描 主要控制点击进入部分
const isScan = ref(false)
// 扫描报告markdown
const markdownExportPaper = ref(`
### 扫描基本信息
- **扫描时间**：2025年07月29日 14:00 - 14:30
- **扫描范围**：服务器全系统
- **扫描工具**：自研漏洞扫描系统 v1.0
- **扫描目标**：CPU、磁盘、内存、网络、系统安全

### 扫描结果概览
本次系统扫描共发现 **6 个漏洞**，其中 **3 个高危漏洞** 和 **3 个中危漏洞**。扫描结果如下：

### 漏洞详情

#### 1. CPU超频过压
- **漏洞名称**：CPU超频过压
- **描述**：CPU频率超过安全阈值，电压＞1.4V且温度＞95℃。长时间运行可能导致系统不稳定，甚至损坏硬件。
- **修复建议**：降低CPU频率，调整电压设置，确保温度在安全范围内。
- **风险等级**：高危

#### 2. 磁盘坏道扩散
- **漏洞名称**：磁盘坏道扩散
- **描述**：磁盘坏道数量持续增加，可能导致数据丢失。建议尽快备份重要数据。
- **修复建议**：使用磁盘修复工具尝试修复坏道，若无法修复则更换磁盘。
- **风险等级**：高危

#### 3. 网络异常流量
- **漏洞名称**：网络异常流量
- **描述**：检测到异常的网络流量，可能存在安全隐患。建议检查网络连接并排查异常流量来源。
- **修复建议**：使用网络监控工具分析流量，关闭不必要的网络服务。
- **风险等级**：中危

#### 4. 内存双通道失效
- **漏洞名称**：内存双通道失效
- **描述**：内存双通道失效，可能导致性能下降，带宽下降50%。建议检查内存模块是否松动或损坏。
- **修复建议**：重新插拔内存模块，若问题依旧则更换内存。
- **风险等级**：中危

#### 5. SSD写入寿命耗尽
- **漏洞名称**：SSD写入寿命耗尽
- **描述**：SSD写入寿命即将耗尽，可能导致数据丢失，突发掉盘风险。建议备份数据并更换SSD。
- **修复建议**：备份重要数据，更换新的SSD。
- **风险等级**：高危

#### 6. 内存XMP超频失败
- **漏洞名称**：内存XMP超频失败
- **描述**：内存XMP超频失败，可能导致系统不稳定。建议检查内存模块是否支持XMP超频。
- **修复建议**：关闭XMP超频功能，使用默认内存频率。
- **风险等级**：中危

### 扫描总结
本次扫描发现了多个高危和中危漏洞，建议立即采取措施修复这些问题，以确保系统的稳定性和安全性。对于高危漏洞，建议优先处理并备份重要数据。对于中危漏洞，建议在不影响业务的情况下逐步修复。

### 后续建议
1. **定期扫描**：建议每周进行一次系统扫描，及时发现并修复潜在问题。
2. **数据备份**：定期备份重要数据，以防硬件故障导致数据丢失。
3. **安全监控**：启用网络和系统安全监控工具，实时检测异常行为。

---

**扫描人员**：系统管理员  
**审核人员**：安全主管
`)
// 扫描报告文字版本
const exportPaper = ref("")

// =============================== 修复窗口和复制功能 ===============================
// 窗口中修复建议文字
const text = ref("")
const { toClipboard } = clipboard3();
const copy = async() => {
  try {
    await toClipboard(text.value);
    alert("复制成功")
  }catch (error) {
    alert("复制失败")
  }
}

// 漏洞列表title
const columns = [
  { title: '漏洞名称', dataIndex: 'title', key: 'title', width: '150px', fixed: 'left' },
  { title: '漏洞详细', dataIndex: 'content', key: 'content' },
  { title: '操作', dataIndex: 'action', key: 'action', width: '250px',fixed: 'right' }
];

// 点击开始扫描
function handleStartScan() {
  loopList.value = []
  isScan.value = !isScan.value;
  isEnd.value = 1;
  loading.value = true;
  initSocket()
//   setTimeout(() => {
//     loopList.value = [
//         { id: 0, loopName: 'CPU超频过压', loopContent: 'CPU频率超过安全阈值，电压＞1.4V且温度＞95℃', loopAction: 'aaa' },
//         { id: 1, loopName: '磁盘坏道扩散', loopContent: '磁盘坏道数量持续增加，可能导致数据丢失', loopAction: 'bbb' },
//         { id: 2, loopName: '网络异常流量', loopContent: '检测到异常的网络流量，可能存在安全隐患', loopAction: 'ccc' },
//         { id: 3, loopName: '内存双通道失效', loopContent: '内存双通道失效，可能导致性能下降，带宽下降50%', loopAction: 'ddd' },
//         { id: 4, loopName: 'SSD写入寿命耗尽', loopContent: 'SSD写入寿命即将耗尽，可能导致数据丢失，突发掉盘风险', loopAction: 'eee' },
//         { id: 5, loopName: '内存XMP超频失败', loopContent: '内存XMP超频失败，可能导致系统不稳定', loopAction: 'fff' }
//       ];
//   }, 1000);
//   setTimeout(() => {
//     isScan.value = !isScan.value;
//     isEnd.value = 2;
//     loading.value = false;
//     markdownExportPaper.value = `
// "# 银河麒麟系统安全审计报告\n\n**报告编号：2025-SR-0801-001**\n\n---\n\n## 一、审计概览\n\n- **系统版本**：4.19.90-89.24.v2401.ky10.x86_64  \n- **Lynis 版本**：3.1.4  \n- **审计时间**：2025-08-01 14:47:11  \n- **安全评分**：23/100（需要立即整改）  \n- **失败检测项**：36项  \n\n---\n\n## 二、关键漏洞分析\n\n### 高危漏洞（风险≥8）\n\n1. **漏洞 ID**: 3  \n   - **名称**: 漏洞4  \n   - **内容**: 测试ID: AUTH-9229  \n   - **建议修复**: 检查PAM配置，增加轮数并设置密码过期策略以加密新值。\n\n2. **漏洞 ID**: 4  \n   - **名称**: 漏洞5  \n   - **内容**: 测试ID: AUTH-9230  \n   - **建议修复**: 配置/etc/login.defs中的密码哈希轮数，确保符合安全标准。\n\n3. **漏洞 ID**: 5  \n   - **名称**: 漏洞6  \n   - **内容**: 测试ID: AUTH-9286  \n   - **建议修复**: 配置最小密码使用期限，在/etc/login.defs中设置合理值。\n\n4. **漏洞 ID**: 6  \n   - **名称**: 漏洞7  \n   - **内容**: 测试ID: AUTH-9286  \n   - **建议修复**: 配置最大密码使用期限，防止密码过期时间过长。\n\n5. **漏洞 ID**: 22  \n   - **名称**: 漏洞23  \n   - **内容**: 测试ID: SSH-7408详细信息: MaxAuthTries (设置为6到3)  \n   - **建议修复**: 考虑强化SSH配置，减少最大认证尝试次数。\n\n6. **漏洞 ID**: 23  \n   - **名称**: 漏洞24  \n   - **内容**: 测试ID: SSH-7408详细信息: MaxSessions (设置为10到2)  \n   - **建议修复**: 考虑强化SSH配置，限制最大会话数。\n\n7. **漏洞 ID**: 24  \n   - **名称**: 漏洞25  \n   - **内容**: 测试ID: SSH-7408详细信息: PermitRootLogin (设置为YES到(FORCED-COMMANDS-ONLY|NO|PROHIBIT-PASSWORD|WITHOUT-PASSWORD))  \n   - **建议修复**: 禁止root用户直接登录，改为使用SSH密钥或其他安全方式。\n\n8. **漏洞 ID**: 25  \n   - **名称**: 漏洞26  \n   - **内容**: 测试ID: SSH-7408详细信息: Port (设置为22到)  \n   - **建议修复**: 更改默认SSH端口，避免默认端口被攻击。\n\n9. **漏洞 ID**: 26  \n   - **名称**: 漏洞27  \n   - **内容**: 测试ID: SSH-7408详细信息: TCPForwarding (设置为YES到NO)  \n   - **建议修复**: 禁用SSH的TCP转发功能，防止滥用。\n\n---\n\n### 中危漏洞（风险5-7）\n\n1. **漏洞 ID**: 31  \n   - **名称**: 漏洞32  \n   - **内容**: 测试ID: ACCT-9628  \n   - **建议修复**: 启用auditd以收集审计信息，增强日志监控能力。\n\n---\n\n## 三、修复建议\n\n### 紧急修复项\n\n1. **证书过期检查**：  \n   - 定期检查系统中所有SSL/TLS证书的有效性，确保无过期情况。  \n   - 建议配置自动提醒机制，提前30天通知即将过期的证书。\n\n2. **关键配置强化**：  \n   - 配置SSH以更严格的安全策略，例如禁用密码认证、启用双因素认证等。\n\n### 加固建议\n\n1. **系统更新与补丁管理**：  \n   - 定期检查并安装操作系统和应用程序的安全补丁，确保系统处于最新状态。  \n   - 使用自动化工具（如YUM/DEB）进行补丁管理。\n\n2. **网络访问控制**：  \n   - 配置防火墙策略，限制不必要的端口和服务访问。  \n   - 实施网络分段策略，隔离关键业务流量。\n\n3. **用户权限管理**：  \n   - 定期审查用户权限，确保最小化原则，避免高权限账户的滥用。  \n   - 启用多因素认证（MFA），增强身份验证安全。\n\n---\n\n## 四、合规性状态\n\n- **PCI-DSS 合规**: 不符合  \n  原因：存在未修复的高危漏洞，尤其是与密码策略和SSH配置相关的漏洞，可能违反 PCI-DSS 要求。\n\n- **等保要求**: 不符合  \n  原因：系统未能达到三级安全保护等级的要求，特别是在身份验证、访问控制和日志管理方面存在明显不足。\n\n---\n\n## 五、行动路线图\n\n### 安全修复计划\n\n1. **现在**：  \n   - 处理证书过期问题。  \n   - 修复所有高危漏洞，特别是与SSH配置和密码策略相关的漏洞。\n\n2. **3天内**：  \n   - 修复中等风险漏洞，例如审计日志审查和优化服务配置。\n\n3. **1周内**：  \n   - 完成PCI-DSS和等保合规性整改，包括实施必要的安全控制措施和优化系统配置。\n\n---\n\n**报告编写人**：SmartCare_for_KylinOS",
//       "scanPaper": "银河麒麟系统安全审计报告\n\n报告编号：2025-SR-0801-001\n  --\n\n一、审计概览\n====================\n  系统版本：4.19.90-89.24.v2401.ky10.x86_64  \n  Lynis 版本：3.1.4  \n  审计时间：2025-08-01 14:47:11  \n  安全评分：23/100（需要立即整改）  \n  失败检测项：36项  \n  --\n\n\n二、关键漏洞分析\n====================\n\n高危漏洞（风险≥8）\n\n1. 漏洞 ID: 3  \n  名称: 漏洞4  \n  内容: 测试ID: AUTH-9229  \n  建议修复: 检查PAM配置，增加轮数并设置密码过期策略以加密新值。\n\n2. 漏洞 ID: 4  \n  名称: 漏洞5  \n  内容: 测试ID: AUTH-9230  \n  建议修复: 配置/etc/login.defs中的密码哈希轮数，确保符合安全标准。\n\n3. 漏洞 ID: 5  \n  名称: 漏洞6  \n  内容: 测试ID: AUTH-9286  \n  建议修复: 配置最小密码使用期限，在/etc/login.defs中设置合理值。\n\n4. 漏洞 ID: 6  \n  名称: 漏洞7  \n  内容: 测试ID: AUTH-9286  \n  建议修复: 配置最大密码使用期限，防止密码过期时间过长。\n\n5. 漏洞 ID: 22  \n  名称: 漏洞23  \n  内容: 测试ID: SSH-7408详细信息: MaxAuthTries (设置为6到3)  \n  建议修复: 考虑强化SSH配置，减少最大认证尝试次数。\n\n6. 漏洞 ID: 23  \n  名称: 漏洞24  \n  内容: 测试ID: SSH-7408详细信息: MaxSessions (设置为10到2)  \n  建议修复: 考虑强化SSH配置，限制最大会话数。\n\n7. 漏洞 ID: 24  \n  名称: 漏洞25  \n  内容: 测试ID: SSH-7408详细信息: PermitRootLogin (设置为YES到(FORCED-COMMANDS-ONLY|NO|PROHIBIT-PASSWORD|WITHOUT-PASSWORD))  \n  建议修复: 禁止root用户直接登录，改为使用SSH密钥或其他安全方式。\n\n8. 漏洞 ID: 25  \n  名称: 漏洞26  \n  内容: 测试ID: SSH-7408详细信息: Port (设置为22到)  \n  建议修复: 更改默认SSH端口，避免默认端口被攻击。\n\n9. 漏洞 ID: 26  \n  名称: 漏洞27  \n  内容: 测试ID: SSH-7408详细信息: TCPForwarding (设置为YES到NO)  \n  建议修复: 禁用SSH的TCP转发功能，防止滥用。\n  --\n\n中危漏洞（风险5-7）\n\n1. 漏洞 ID: 31  \n  名称: 漏洞32  \n  内容: 测试ID: ACCT-9628  \n  建议修复: 启用auditd以收集审计信息，增强日志监控能力。\n  --\n\n\n三、修复建议\n====================\n\n紧急修复项\n\n1. 证书过期检查：  \n  定期检查系统中所有SSL/TLS证书的有效性，确保无过期情况。  \n  建议配置自动提醒机制，提前30天通知即将过期的证书。\n\n2. 关键配置强化：  \n  配置SSH以更严格的安全策略，例如禁用密码认证、启用双因素认证等。\n\n加固建议\n\n1. 系统更新与补丁管理：  \n  定期检查并安装操作系统和应用程序的安全补丁，确保系统处于最新状态。  \n  使用自动化工具（如YUM/DEB）进行补丁管理。\n\n2. 网络访问控制：  \n  配置防火墙策略，限制不必要的端口和服务访问。  \n  实施网络分段策略，隔离关键业务流量。\n\n3. 用户权限管理：  \n  定期审查用户权限，确保最小化原则，避免高权限账户的滥用。  \n  启用多因素认证（MFA），增强身份验证安全。\n  --\n\n\n四、合规性状态\n====================\n  PCI-DSS 合规: 不符合  \n  原因：存在未修复的高危漏洞，尤其是与密码策略和SSH配置相关的漏洞，可能违反 PCI-DSS 要求。\n  等保要求: 不符合  \n  原因：系统未能达到三级安全保护等级的要求，特别是在身份验证、访问控制和日志管理方面存在明显不足。\n  --\n\n\n五、行动路线图\n====================\n\n安全修复计划\n\n1. 现在：  \n  处理证书过期问题。  \n  修复所有高危漏洞，特别是与SSH配置和密码策略相关的漏洞。\n\n2. 3天内：  \n  修复中等风险漏洞，例如审计日志审查和优化服务配置。\n\n3. 1周内：  \n  完成PCI-DSS和等保合规性整改，包括实施必要的安全控制措施和优化系统配置。\n  --\n\n报告编写人：SmartCare_for_KylinOS`
//   },3000)
}

// 深监听列表，如果为空则loading持续为false，并且将loopList反转
watch(loopList, (newVal) => {
  // if (!loopList.value) {
  //   loading.value = false
  // }
  loopListRe.value = [...newVal].reverse();
  console.log(loopListRe.value);
  
},{deep: true})
watch(loading,(newVal) => {
  if (!newVal) {
    isScan.value = !isScan.value;
    isEnd.value = 2
    socket.value.on('disconnect', ( reason) => {
      console.log("Socket.IO 断开连接:", reason);
    })
  }
}, {deep: true})

// 模拟后端推送的数据
// function onDataUpdate(newDataForLoop) {
//   // 更新 loopList
//   newDataForLoop.forEach((item) => {
//       loopList.value.push(item);
//   });
// }

// ========================= 导出报告文档 =========================
const exportScanPapper = () => {
  loadFile(`${"/abc"}.docx`,( error,content) => {
    if (error) {
      throw error;
    }
    const zip = new PizZip(content)
    const doc = new Docxtemplater(zip, {
      paragraphLoop: true,
      linebreaks: true
    });
    const data = {
      content: exportPaper.value
    };
    doc.setData(data);
    try {
      doc.render();
    }catch (e) {
      console.error(e);
      return
    }
    const out = doc.getZip().generate({
      type:'blob',
      mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    });
    saveAs(out,'修复报告.docx')
  })
  console.log(exportPaper.value);
  
}
function loadFile(url, callback) {
  PizZipUtils.getBinaryContent(url, callback);
}

// ========================= 将markdown改成文字 =========================
const renderMarkdown = (content) => {
  return marked(content);
}
// 变成str （暂时不用）
// const renderMarkdownToStr = (content) => {
//   return marked.parse(content)
// }

// ========================= 接口部分 =========================
const initSocket = () => {
  socket.value = io('http://192.168.31.96:5000', {
    transports: ['websocket', 'polling'],
    reconnection: true,
    reconnectionAttempts: Infinity,
    reconnectionDelay: 1000
  });

  // 连接错误
  socket.value.on('connect_error', (error) => {
    console.error('WebSocket连接错误', error);
  });
  // 连接成功
  socket.value.on('connect', () => {
    console.log("Socket.IO连接成功");
    socket.value.emit("client_server3", "scanBoard");
  })
  socket.value.on('alert_update3', (payload) => {
    try {
      const parsed = typeof payload === 'string' ? JSON.parse(payload) : payload;
      console.log('解析后的数据', parsed);
      if (parsed?.code === 200) {
        data.value = deepCopy(parsed);
        console.log('data 已更新:',data.value);
        updateData(data.value);
      } else {
        console.error('无效的数据格式',parsed);
      }
    } catch (e) {
      console.error('数据处理失败', e);
    }
  })
}
function updateData(parsedData) {
  loopList.value = parsedData.data?.loopList || null;
  loading.value = parsedData.data?.endCon.loading;
  markdownExportPaper.value = parsedData.data?.endCon.scanPaperMarkdown || "";
  exportPaper.value = parsedData.data?.endCon.scanPaper || "";
  console.log(loopList.value);
  console.log(loading.value);
  console.log(markdownExportPaper.value);
  console.log(exportPaper.value);
  
  
  
  
}
// 深拷贝函数
function deepCopy(obj) {
    return JSON.parse(JSON.stringify(obj));
}
</script>


<template>
  <div class="scanBoard-content">
    <h1>漏洞扫描</h1>
    <div class="scaning-card">
      <div class="scaning-load">
        <div class="cube-loader">
          <div class="cube-top"></div>
          <div class="cube-wrapper">
            <span style="--i:0" class="cube-span"></span>
            <span style="--i:1" class="cube-span"></span>
            <span style="--i:2" class="cube-span"></span>
            <span style="--i:3" class="cube-span"></span>
          </div>
        </div>
        <div class="overlay" @click="handleStartScan" v-if="!isScan">
          <span class="loading-text">点击开始扫描</span>
        </div>
        <div class="overlay" v-else>
          <span class="loading-text">正在扫描</span>
        </div>
      </div>
      <div class="scaning-item">
        <!-- 只显示当前索引对应的数据 -->
        <div class="scaning-item-content">
          <h2 class="scaning-content-title" v-if="isEnd === 0">
            点击进入扫描
          </h2>
          <h2 class="scaning-content-title" v-else-if="isEnd === 1">
            正在扫描并生成报告
          </h2>
          <h2 class="scaning-content-title" v-else>
            生成报告:
          </h2>
          <div class="scaning-item-btn">
            <el-button v-if="isEnd === 0" disabled type="primary">查看扫描报告</el-button>
            <el-button v-else-if="isEnd === 1"  loading type="primary">查看扫描报告</el-button>
            <el-button v-else @click="scanPaperVisible = true" class="mr" type="primary">查看扫描报告</el-button>
            <el-button v-if="isEnd === 0" disabled type="success">导出报告</el-button>
            <el-button v-else-if="isEnd === 1" loading type="success">导出报告</el-button>
            <el-button v-else @click="exportScanPapper" type="success">导出报告</el-button>
          </div>
        </div>
      </div>
    </div>
    <div class="scaning-loopList">
      <a-table v-loading="loading" :columns="columns" :data-source="loopListRe" bordered class="scaning-loopList-table" :pagination="{ pageSize: 10 }" :scroll="{ x: 'max-content' }">
        <template #bodyCell="{ column, record }">
          <span v-if="column.key === 'title'">{{ record.loopName }}</span>
          <span v-else-if="column.key === 'content'">{{ record.loopContent }}</span>
          <span v-else-if="column.key === 'action'">
            <a-tooltip title="查看修复建议" color="#107ee9">
              <a-button type="primary" size="default" @click="centerDialogVisible = true;text = record.loopAction" class="mr-2" plain>修复建议</a-button>
            </a-tooltip>
          </span>
        </template>
      </a-table>
    </div>
  </div>
  <el-dialog
    v-model="centerDialogVisible"
    title="修复建议"
    width="500"
    align-center
  >
    <div>{{ text }}</div>
    <template #footer>
      <el-button @click="copy" type="primary" round class="copy-btn">点击复制</el-button>
      <el-button @click="centerDialogVisible = false" type="info" round class="copy-btn">关闭</el-button>
    </template>
  </el-dialog>
  <el-dialog
    v-model="scanPaperVisible"
    title="扫描报告"
    width="900"
  >
  <div class="paper-content" v-html="renderMarkdown(markdownExportPaper)"></div>
  <template #footer>
    <el-button @click="scanPaperVisible = false" type="primary" round class="copy-btn">关闭</el-button>
  </template>
  </el-dialog>
</template>

<style scoped>
.scanBoard-content {
  width: 100%;
}
.scanBoard-content h1 {
  font-size: 36px;
  color: rgb(0, 81, 255);
  margin-bottom: 20px;
  font-weight: 750;
}
.fixLoopContent {
  padding: 20px;
  width: 100%;
  height: 100%;
}

.scaning-card {
  display: flex;
  width: 100%;
  height: 200px;
  justify-content: space-between;
  align-items: center;
}
.scaning-card .scaning-load {
  position: relative;
  width: 30%;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  cursor: pointer;
}



.scaning-card .scaning-load .overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(255, 255, 255, 0.7);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1;
  opacity: 0;
  transition: opacity 0.5s ease;
  font-size: 20px;
  color: #323232;
  font-weight: 700;
  animation: blink 1.5s infinite;
}
.scaning-load:hover .overlay {
  opacity: 1;
  animation: none;
}
.overlay .loading-text {
  position: absolute;
  bottom: 10;
  color: #000;
}


.scaning-card .scaning-item {
  display: flex;
  height: 100%;
  width: 70%;
  /* background-color: #000; */
}
.scaning-item .scaning-item-content{
  width: 100%;
  height: 100%;
  color: #000;
  box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
  padding: 20px;
  border-radius: 20px;
}
.scaning-item-content .scaning-content-title {
  font-size: 24px;
  color: rgb(0, 81, 255);
  margin-bottom: 10px;
  font-weight: 700; 
  display: inline-block;
}

.scaning-item-content .scaning-item-btn {
  width: 100%;
  height: 80%;
  text-align: center;
  padding: 50px 0 0;
}
.el-button, .el-button.is-round {
  padding: 10px 20px;
}
.el-button {
  padding: 10px 15px !important;
  font-size: 20px;
  font-weight: 800;  
}
.el-button.copy-btn {
  font-size: 14px;
  font-weight: normal;
}
.mr {
  margin-right: 50px;
}
.scaning-loopList {
  width: 100%;
  margin-top: 20px;
  box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
  padding: 20px;
  border-radius: 20px;
}

.fixLoop-card .ant-descriptions-item-label {
  font-size: 16px; /* 调整标签字体大小 */
  font-weight: 600; /* 增加标签字体权重 */
  white-space: nowrap; /* 防止标签换行 */
}

.fixLoop-card .ant-descriptions-item-content {
  font-size: 14px; /* 调整内容字体大小 */
  white-space: normal; /* 允许内容换行 */
  word-wrap: break-word; /* 自动换行 */
  overflow-wrap: break-word; /* 兼容性换行 */
}

.fixLoop-card .ant-descriptions-view {
  display: flex;
  flex-wrap: wrap; /* 允许子项换行 */
  gap: 10px; /* 增加子项之间的间距 */
}

.fixLoop-card .ant-descriptions-item {
  flex: 1 1 calc(50% - 10px); /* 每行显示两列，间距为 10px */
  min-width: 200px; /* 设置最小宽度，防止过小 */
}

  .cube-loader {
    position: relative;
  /* u can choose any size */
    width: 75px;
    height: 75px;
    transform-style: preserve-3d;
    transform: rotateX(-30deg);
    animation: animate 4s linear infinite;
  }
  .mr-2 {
    margin-right: 15px;
  }
.animate-text {
    animation: slideIn 1s cubic-bezier(0.175,0.885,0.32,1.275) forwards;
    transform: translateY(-100%);
    opacity: 0;
  }
.paper-content {
  margin: 20px 30px;
}

/* 针对宽度小于 1350px 的屏幕 */
@media (max-width: 1350px) {
  .scanBoard-content h1 {
    font-size: 32px; /* 调整标题字体大小 */
    margin-bottom: 15px;
  }

  .scaning-card {
    flex-direction: column; /* 改为垂直布局 */
    height: auto; /* 自适应高度 */
  }

  .scaning-card .scaning-load {
    width: 100%; /* 占满宽度 */
    height: 150px; /* 调整高度 */
    margin-bottom: 20px; /* 增加间距 */
  }

  .scaning-card .scaning-item {
    width: 100%; /* 占满宽度 */
    height: auto; /* 自适应高度 */
  }

  .scaning-item-content {
    padding: 15px; /* 减小内边距 */
    border-radius: 15px; /* 调整圆角 */
  }

  .scaning-item-content .scaning-content-title {
    font-size: 20px; /* 调整标题字体大小 */
  }
  .scaning-loopList {
    padding: 15px; /* 减小内边距 */
    border-radius: 15px; /* 调整圆角 */
  }
}

/* 针对宽度小于 750px 的屏幕 */
@media (max-width: 750px) {
  .scanBoard-content h1 {
    font-size: 28px; /* 调整标题字体大小 */
    text-align: center; /* 居中标题 */
    margin-bottom: 10px;
  }

  .scaning-card {
    flex-direction: column; /* 改为垂直布局 */
    height: auto; /* 自适应高度 */
  }

  .scaning-card .scaning-load {
    width: 100%; /* 占满宽度 */
    height: 120px; /* 调整高度 */
    margin-bottom: 15px; /* 增加间距 */
  }

  .scaning-card .scaning-item {
    width: 100%; /* 占满宽度 */
    height: auto; /* 自适应高度 */
  }

  .scaning-item-content {
    padding: 10px; /* 减小内边距 */
    border-radius: 10px; /* 调整圆角 */
  }

  .scaning-item-content .scaning-content-title {
    font-size: 18px; /* 调整标题字体大小 */
  }

  .scaning-loopList {
    padding: 10px; /* 减小内边距 */
    border-radius: 10px; /* 调整圆角 */
  }

  .scaning-loopList .scaning-loopList-table {
    font-size: 12px; /* 调整表格字体大小 */
  }

  .overlay .loading-text {
    font-size: 16px; /* 调整加载文字大小 */
  }
   .fixLoop-card .ant-descriptions-item {
    flex: 1 1 100%; /* 每行显示一列 */
  }

  .fixLoop-card .ant-descriptions-item-label {
    font-size: 14px; /* 减小标签字体大小 */
  }

  .fixLoop-card .ant-descriptions-item-content {
    font-size: 12px; /* 减小内容字体大小 */
  }
}

  @keyframes blink {
    0% {
      opacity: 0.3;
    }
    50% {
      opacity: 0.75;
    }
    100% {
      opacity: 0.3;
    }
  }


  @keyframes slideIn {
    0% {
      transform: translateY(100%);
      opacity: 0;
    }

    100% {
      transform: translateY(0);
      opacity: 1;
    }
  }
  @keyframes animate {
    0% {
      transform: rotateX(-30deg) rotateY(0);
    }

    100% {
      transform: rotateX(-30deg) rotateY(360deg);
    }
  }

  .cube-loader .cube-wrapper {
    width: 100%;
    height: 100%;
    transform-style: preserve-3d;
  }

  .cube-loader .cube-wrapper .cube-span {
    position: absolute;
    width: 100%;
    height: 100%;
    transform: rotateY(calc(90deg * var(--i))) translateZ(37.5px);
    background: linear-gradient(
      to bottom,
      hsl(330, 3.13%, 25.1%) 0%,
      hsl(177.27, 21.71%, 32.06%) 5.5%,
      hsl(176.67, 34.1%, 36.88%) 12.1%,
      hsl(176.61, 42.28%, 40.7%) 19.6%,
      hsl(176.63, 48.32%, 43.88%) 27.9%,
      hsl(176.66, 53.07%, 46.58%) 36.6%,
      hsl(176.7, 56.94%, 48.91%) 45.6%,
      hsl(176.74, 62.39%, 50.91%) 54.6%,
      hsl(176.77, 69.86%, 52.62%) 63.4%,
      hsl(176.8, 76.78%, 54.08%) 71.7%,
      hsl(176.83, 83.02%, 55.29%) 79.4%,
      hsl(176.85, 88.44%, 56.28%) 86.2%,
      hsl(176.86, 92.9%, 57.04%) 91.9%,
      hsl(176.88, 96.24%, 57.59%) 96.3%,
      hsl(176.88, 98.34%, 57.93%) 99%,
      hsl(176.89, 99.07%, 58.04%) 100%
    );
  }

  .cube-top {
    position: absolute;
    width: 75px;
    height: 75px;
    background: hsl(330, 3.13%, 25.1%) 0%;
                        /* width 75px / 2 = 37.5px */
    transform: rotateX(90deg) translateZ(37.5px);
    transform-style: preserve-3d;
  }

  .cube-top::before {
    content: '';
    position: absolute;
  /* u can choose any size */
    width: 75px;
    height: 75px;
    background: hsl(176.61, 42.28%, 40.7%) 19.6%;
    transform: translateZ(-90px);
    filter: blur(10px);
    box-shadow: 0 0 10px #323232,
                0 0 20px hsl(176.61, 42.28%, 40.7%) 19.6%,
                0 0 30px #323232,
                0 0 40px hsl(176.61, 42.28%, 40.7%) 19.6%;
  }
</style>
